﻿using AutoMapper;
using Mes.Infrastructure.Data.BoundedContext.Extensions;
using Mes.Domain.BoundedContext;
using Microsoft.EntityFrameworkCore;
using System.Linq.Expressions;
using Mes.Application.Common;
using Mes.Infrastructure.Data.BoundedContext.Configurations;
using Microsoft.Extensions.Options;
using Mes.Application.Common.Resource;
using Mes.Application.DTO.Inputs.MasterData;
using Mes.Application.DTO.Results.MasterData;
using Mes.Domain.BoundedContext.Entities.MasterData;
using Mes.Infrastructure.Data.BoundedContext.Repositories.MasterData;

namespace Mes.Application.Services.MasterData
{
    public class MaterialBOMService : IMaterialBOMService
    {
        private readonly IMapper _mapper;
        private readonly IMaterialRepository _materialRepository;
        private readonly IMaterialBOMRepository _materialBOMRepository;
        private readonly IMaterialBOMMaterialLinkRepository _materialBOMMaterialLinkRepository;
        private readonly IMaterialMaterialBOMLinkRepository _materialMaterialBOMLinkRepository;
        private readonly BuiltInTypesConfiguration _builtInTypes;

        public MaterialBOMService(IMapper mapper, IMaterialRepository materialRepository, IMaterialBOMRepository materialBOMRepository, IMaterialBOMMaterialLinkRepository materialBOMMaterialLinkRepository,
            IMaterialMaterialBOMLinkRepository materialMaterialBOMLinkRepository, IOptions<BuiltInTypesConfiguration> options)
        {
            if (mapper == null || materialRepository == null || materialBOMRepository == null || materialBOMMaterialLinkRepository == null
                || materialMaterialBOMLinkRepository == null)
            {
                throw new ArgumentNullException();
            }
            _mapper = mapper;
            _materialRepository = materialRepository;
            _materialBOMRepository = materialBOMRepository;
            _materialBOMMaterialLinkRepository = materialBOMMaterialLinkRepository;
            _materialMaterialBOMLinkRepository = materialMaterialBOMLinkRepository;
            _builtInTypes = options.Value;
        }

        /// <summary>
        /// 新增物料清单
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        /// <exception cref="CustomException"></exception>
        public async Task AddMaterialBOMAsync(AddMaterialBOMInput input)
        {
            var materialIds = input.Materials.Select(x => x.MaterialId).ToList();
            if (materialIds.Count != materialIds.Distinct().Count())
            {
                throw new CustomException(Messages.MaterialDuplication);
            }

            var materials = await _materialRepository.Query().Where(x => materialIds.Contains(x.Id) && x.MaterialClassTypeName == _builtInTypes.BuiltInMaterialClassTypes[1]).ToListAsync();
            if (materials.Count != materialIds.Count)
            {
                throw new CustomException(Messages.MaterialClassTypeNotConsumables);
            }
            foreach (var item in input.Materials)
            {
                var currentMaterial = materials.FirstOrDefault(x => x.Id == item.MaterialId);
                item.MaterialNumber = Math.Round(item.MaterialNumber, currentMaterial?.PrecisionPlace ?? 2, MidpointRounding.AwayFromZero);
            }

            if (await _materialBOMRepository.ExistsAsync(x => x.Code == input.Code || x.Name == input.Name))
            {
                throw new CustomException(Messages.MaterialBOMExist);
            }

            var bom = _mapper.Map<MaterialBOM>(input);
            await _materialBOMRepository.AddAsync(bom);
            int status = await _materialBOMRepository.SaveAsync();
            if (status <= 0)
            {
                throw new CustomException(Messages.SavingFailure);
            }
            if (input.Materials.Count > 0)
            {
                var links = new List<MaterialBOMMaterialLink>();
                foreach (var item in input.Materials)
                {
                    var link = new MaterialBOMMaterialLink
                    {
                        MaterialBOMId = bom.Id,
                        MaterialId = item.MaterialId,
                        MaterialNumber = item.MaterialNumber,
                    };
                    links.Add(link);
                }
                await _materialBOMMaterialLinkRepository.AddRangeAsync(links);
                status = await _materialBOMMaterialLinkRepository.SaveAsync();
                if (status <= 0)
                {
                    throw new CustomException(Messages.SavingFailure);
                }
            }
        }

        /// <summary>
        /// 编辑物料清单
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        /// <exception cref="CustomException"></exception>
        public async Task UpdateMaterialBOMAsync(UpdateMaterialBOMInput input)
        {
            var bom = await _materialBOMRepository.FindAsync(x => x.Id == input.Id);
            if (bom == null)
            {
                throw new CustomException(Messages.MaterialBOMNotFound);
            }
            var materialIds = input.Materials.Select(x => x.MaterialId).ToList();
            if (materialIds.Count != materialIds.Distinct().Count())
            {
                throw new CustomException(Messages.MaterialDuplication);
            }
            var materials = await _materialRepository.Query().Where(x => materialIds.Contains(x.Id) && x.MaterialClassTypeName == _builtInTypes.BuiltInMaterialClassTypes[1]).ToListAsync();
            if (materials.Count != materialIds.Count)
            {
                throw new CustomException(Messages.MaterialClassTypeNotConsumables);
            }
            foreach (var item in input.Materials)
            {
                var currentMaterial = materials.FirstOrDefault(x => x.Id == item.MaterialId);
                item.MaterialNumber = Math.Round(item.MaterialNumber, currentMaterial?.PrecisionPlace ?? 2, MidpointRounding.AwayFromZero);
            }
            bom.Remark = input.Remark;
            bom.Version = input.Version;
            bom.Updatetime = DateTime.Now;
            await _materialBOMMaterialLinkRepository.DeleteAsync(x => x.MaterialBOMId == input.Id);
            if (input.Materials.Count > 0)
            {
                var links = new List<MaterialBOMMaterialLink>();
                foreach (var item in input.Materials)
                {
                    var link = new MaterialBOMMaterialLink
                    {
                        MaterialBOMId = input.Id,
                        MaterialId = item.MaterialId,
                        MaterialNumber = item.MaterialNumber,
                    };
                    links.Add(link);
                }
                await _materialBOMMaterialLinkRepository.AddRangeAsync(links);
            }
            int status = await _materialBOMMaterialLinkRepository.SaveAsync();
            if (status <= 0)
            {
                throw new CustomException(Messages.SavingFailure);
            }
        }

        /// <summary>
        /// 删除物料清单
        /// </summary>
        /// <param name="ids"></param>
        /// <returns></returns>
        public async Task DeleteMaterialBOMsAsync(IList<int> ids)
        {
            await _materialBOMRepository.DeleteAsync(x => ids.Contains(x.Id));
            await _materialBOMMaterialLinkRepository.DeleteAsync(x => ids.Contains(x.MaterialBOMId));
            await _materialMaterialBOMLinkRepository.DeleteAsync(x => ids.Contains(x.MaterialBOMId));
            int status = await _materialBOMMaterialLinkRepository.SaveAsync();
            if (status <= 0)
            {
                throw new CustomException(Messages.SavingFailure);
            }
        }

        /// <summary>
        /// 分页查询物料清单列表
        /// </summary>
        /// <param name="input"></param>
        /// <returns>PagedItemsData<MaterialBOMResult></returns>
        public async Task<PagedItemsData<MaterialBOMResult>> GetMaterialBOMsAsync(GetMaterialBOMsInput input)
        {
            Expression<Func<MaterialBOM, bool>> filter = x => true;
            if (!string.IsNullOrEmpty(input.KeyWord))
            {
                filter = filter.And(x => x.Name.Contains(input.KeyWord) || x.Code.Contains(input.KeyWord));
            }
            var data = await _materialBOMRepository.Query().Where(filter).OrderByDescending(x => x.CreateTime).ToPagedAsync(input.PagedInput);
            if (data == null)
            {
                throw new CustomException(Messages.MaterialBOMNotFound);
            }
            var result = _mapper.Map<PagedItemsData<MaterialBOMResult>>(data);
            var materialBOMIds = result.Items.Select(x => x.Id).ToList();
            if (materialBOMIds.Count > 0)
            {
                var links = await _materialBOMMaterialLinkRepository.Query().Where(x => materialBOMIds.Contains(x.MaterialBOMId)).ToListAsync();
                var materialIds = links.Select(x => x.MaterialId).ToList();
                if (materialIds.Count > 0)
                {
                    var materials = await _materialRepository.Query().Where(x => materialIds.Contains(x.Id)).ToListAsync();
                    foreach (var item in result.Items)
                    {
                        var currentLinks = links.Where(x => x.MaterialBOMId == item.Id).ToList();
                        var currentMaterialIds = currentLinks.Select(x => x.MaterialId).ToList();
                        var currentMaterials = materials.Where(x => currentMaterialIds.Contains(x.Id)).ToList();
                        item.Materials = _mapper.Map<List<MaterialResult>>(currentMaterials);
                        foreach (var material in item.Materials)
                        {
                            material.MaterialNumber = currentLinks.FirstOrDefault(x => x.MaterialId == material.Id && x.MaterialBOMId == item.Id)?.MaterialNumber ?? 0;
                        }
                    }
                }
            }
            return result;
        }

        /// <summary>
        /// 查询物料清单实体
        /// </summary>
        /// <param name="id"></param>
        /// <returns>MaterialBOMResult</returns>
        /// <exception cref="CustomException"></exception>
        public async Task<MaterialBOMResult> GetMaterialBOMAsync(int id)
        {
            var bom = await _materialBOMRepository.FindAsync(x => x.Id == id);
            if (bom == null)
            {
                throw new CustomException(Messages.MaterialBOMNotFound);
            }
            var links = await _materialBOMMaterialLinkRepository.Query().Where(x => x.MaterialBOMId == id).ToListAsync();
            var result = _mapper.Map<MaterialBOMResult>(bom);
            var materialIds = links.Select(x => x.MaterialId).ToList();
            if (materialIds.Count > 0)
            {
                var materials = await _materialRepository.Query().Where(x => materialIds.Contains(x.Id)).ToListAsync();
                result.Materials = _mapper.Map<List<MaterialResult>>(materials);
                foreach (var item in result.Materials)
                {
                    item.MaterialNumber = links.FirstOrDefault(x => x.MaterialId == item.Id)?.MaterialNumber ?? 0;
                }
            }
            return result;
        }

        /// <summary>
        /// 移除物料清单下的物料
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        /// <exception cref="CustomException"></exception>
        public async Task RemoveMaterialOfMaterialBOMAsync(RemoveMaterialOfMaterialBOMInput input)
        {
            await _materialBOMMaterialLinkRepository.DeleteAsync(x => x.MaterialId == input.MaterialId && x.MaterialBOMId == input.MaterialBOMId);
            int status = await _materialBOMMaterialLinkRepository.SaveAsync();
            if (status <= 0)
            {
                throw new CustomException(Messages.SavingFailure);
            }
        }
    }
}
